﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.ComponentModel;
using System.Threading;
using System.Xml.Linq;

namespace ResourceManager
{
    /// <summary>
    /// 资源下载器
    /// </summary>
    class Downloader
    {
        const int BUFFER_SIZE = 1024 * 10;
        const string SUFFIX_RESUME = ".dmz.cfg";

        public event EventHandler<LoadResourceCompletedEventArgs>       DownloadCompleted = delegate { };
        public event EventHandler<LoadResourceProgressChangedEventArgs> DownloadProgressChanged = delegate { };

        /// <summary>下载缓冲</summary>
        private byte[] _buffer = new byte[BUFFER_SIZE];
        /// <summary>已经下载的长度</summary>
        private int _downloadedLength;

        private Stream _writer;
        private Stream _reader;
        
        /// <summary>连接请求</summary>
        private WebRequest _request;
        private WebResponse _response;

        /// <summary>资源名</summary>
        private string _resourceName;

        /// <summary>文件名</summary>
        private string _filename;

        private int _timeout = 0;
        private bool _canceled = false;

        private Config _config;

        public void DownloadAsync(string resourceName, object userState, Config config)
        {
            _config = config;
            _filename = _config.LocalPath + resourceName;
            _resourceName = resourceName;
            _timeout = _config.Timeout;

            if (File.Exists(_filename + SUFFIX_RESUME))
            {
                DownloadAsyncFromCfg(_filename + SUFFIX_RESUME, resourceName);
                return;
            }
            string url = null;
            switch (_config.RemoteType)
            {
                case RemoteType.FILE:
                    url = "file://" + config.RemoteIP + config.RemotePath + resourceName;
                    break;
                case RemoteType.HTTP:
                    url = "http://" + config.RemoteIP + ":" + config.RemotePort + config.RemotePath + resourceName;
                    break;
                default:
                    break;
            }

            _request = WebRequest.Create(url);
            _request.Method = "GET";
            

            IAsyncResult ar = _request.BeginGetResponse(OnResponseDone, userState);
            ThreadPool.RegisterWaitForSingleObject(ar.AsyncWaitHandle, OnTimeout, _request, _timeout, true);
        }

        private void DownloadAsyncFromCfg(string downloadCfg, string resourceName)
        {
            string url = null;
            if (RemoteType.HTTP == _config.RemoteType)
            {
                url = "http://" + _config.RemoteIP + ":" + _config.RemotePort + _config.RemotePath + resourceName;
            }
            else
            {

            }
               
            _request = WebRequest.Create(url);
            ResumeCfg cfg = new ResumeCfg(downloadCfg);
            string fileMD5 = VersionController.GetFileMD5(_filename);
            if (fileMD5 != null && fileMD5 == cfg.FileMD5)
            {
   //             _request.Headers.Add(HttpRequestHeader.IfModifiedSince, "dddd");
                ((HttpWebRequest)_request).AddRange(int.Parse(cfg.FileSize));
                _downloadedLength += int.Parse(cfg.FileSize);
            }
            IAsyncResult ar = _request.BeginGetResponse(OnResponseDone, null);
        }

        public void CancelAsync()
        {
            _canceled = true;
        }

        private void OnTimeout(object o, bool timeout)
        {
            if (timeout)
            {
                WebRequest request = o as WebRequest;
                if (request != null)
                {
                    _request.Abort();
                }
            }
        }
        
        private void OnResponseDone(IAsyncResult ar)
        {
            _response = _request.EndGetResponse(ar);
            
            HttpWebResponse http = _response as HttpWebResponse;
            if (http != null)
            {
                switch (http.StatusCode)
                {
                    case HttpStatusCode.PartialContent:
                        _writer = new FileStream(_filename, FileMode.Append);
                        break;
                    case HttpStatusCode.OK:
                        _writer = new FileStream(_filename, FileMode.Create);
                        _writer.SetLength(_response.ContentLength);
                        _writer.Seek(0, SeekOrigin.Begin);
                        break;
                    default:
                        break;
                }
            }
            string a = _response.Headers[HttpResponseHeader.LastModified];
            _reader = _response.GetResponseStream();
            _reader.BeginRead(_buffer, 0, BUFFER_SIZE, OnDownloading, ar.AsyncState);
        }

        private void OnDownloading(IAsyncResult ar)
        {
            if (_canceled)
            {
                OnDownloadDone(ar, true);
                return;
            }
            int len = _reader.EndRead(ar);
            if (len > 0)
            {
                _downloadedLength += len;
                _writer.Write(_buffer, 0, len);
                _reader.BeginRead(_buffer, 0, BUFFER_SIZE, OnDownloading, ar.AsyncState);
                DownloadProgressChanged(this, new LoadResourceProgressChangedEventArgs(_downloadedLength, ar.AsyncState, _filename));
                return;
            }
            OnDownloadDone(ar, false);
        }

        private void OnDownloadDone(IAsyncResult ar, bool canceled)
        {
            if (canceled)
            {
               GenerateResumeCfg();
            }
            _reader.Close();
            _writer.Close();

            EventHandler<LoadResourceCompletedEventArgs> local = DownloadCompleted;
            if (null != local)
            {
                local(this, new LoadResourceCompletedEventArgs(null, canceled, ar.AsyncState, _resourceName));
            }

        }
        private void GenerateResumeCfg()
        {
            System.Security.Cryptography.HashAlgorithm hashAlgo = new System.Security.Cryptography.MD5CryptoServiceProvider();
            _writer.Seek(0, SeekOrigin.Begin);
            byte[] file_md5 = hashAlgo.ComputeHash(_writer);
            StringBuilder sb = new StringBuilder();
            foreach (byte tmp in file_md5)
            {
                sb.Append(tmp.ToString("X2"));
            }
            ResumeCfg cfg = new ResumeCfg();
            cfg.Generate(sb.ToString(), _response.Headers[HttpResponseHeader.LastModified], _downloadedLength, _filename + SUFFIX_RESUME);
        }
    }


    class ResumeCfg
    {
        public ResumeCfg()
        {

        }
        public ResumeCfg(string cfg)
        {
            Load(cfg);
        }

        public void Generate(string fileMD5, string lastModified, int filesize, string filename)
        {
            var cfg = new XDocument(
                        new XElement("config",
                            new XElement("file_md5", fileMD5),
                            new XElement("last_modifed", lastModified),
                            new XElement("file_size", filesize)
                        )
                      );
            cfg.Save(filename);
            Console.WriteLine(cfg.ToString());
        }


        private void Load(string cfg)
        {
            _cfg_root = XElement.Load(cfg);
        }

        public string LastModifed
        {
            get { return _cfg_root.Element("last_modifed").Value; }
        }

        public string FileMD5
        {
            get { return _cfg_root.Element("file_md5").Value; }
        }

        public string FileSize
        {
            get { return _cfg_root.Element("file_size").Value; }
        }

        private XElement _cfg_root;
    }
}
